using Red.Controls.Shell;
using Red.Core;
using Red.EngineData.View;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Red.Controls.ViewModels;

namespace Red.Controls.Utils
{
    /// <summary>
    ///     Bunch of methods to deal with engine resources via Interop
    /// </summary>
    public class ResourceFactory
    {
        #region Internal types

        /// <summary>
        ///     Resource importer data
        /// </summary>
        [InteropType( "ImportExportFileFormatProxy" )]
        public class Importer
        {
            [InteropType( "FileFormatProxy" )]
            public struct SourceFileDef
            {
                [InteropProperty( "ext" )]
                public string Extension { get; set; }

                [InteropProperty( "desc" )]
                public string Description { get; set; }
            }

            [InteropProperty( "formats" )]
            public List<SourceFileDef> SourceFileDefs { get; set; }

            [InteropProperty( "class" )]
            public string ClassName { get; set; }

            [InteropProperty( "ext" )]
            public string Extension { get; set; }
        }

        /// <summary>
        ///     Resource factory data
        /// </summary>
        [InteropType( "FactoryInfoProxy" )]
        public class Factory
        {
            [InteropProperty( "class" )]
            public string ClassName { get; set; }

            [InteropProperty( "ext" )]
            public string Extension { get; set; }

            [InteropProperty( "name" )]
            public string FriendlyName { get; set; }
        }

        #endregion Internal types

        #region Data

        private readonly EditorSharedData _sharedData;
        private readonly AsyncLock _importersLock = new AsyncLock();
        private readonly AsyncLock _factoriesLock = new AsyncLock();
        private List<Importer> _importers;
        private List<Factory> _factories;

        #endregion Data

        public Task<List<Importer>> Importers
        {
            get { return GetImporters(); }
        }

        public Task<List<Factory>> Factories
        {
            get { return GetFactories(); }
        }

        #region C-tor

        public ResourceFactory( EditorSharedData sharedData )
        {
            _sharedData = sharedData;
        }

        #endregion C-tor

        #region Public methods

        /// <summary>
        /// Creates a resource under the specified path( needs to be translatable to depot path )
		/// Factory for the resource is chosen based on the file extension 
        /// </summary>
        /// <param name="fullPath"></param>
        public async Task<Result> Create( string fullPath )
        {
            // NOTE: we use interop path syntax anyway, so it's ok to operate on InteropDataItem level                    
            InteropDataItem engineData = await _sharedData.EngineObjectRoot;

            string depotPath = _sharedData.DepotInfo.GetDepotPath( fullPath );
            InteropDataItem fileItem = (InteropDataItem)await engineData.DigUpSubItemAsync( "depot.files[\"" + depotPath + "\"]" );

            bool createRes = fileItem.IsValid ? ( await fileItem.ExecuteAsync<bool>( "create", "" ) ).Value : false;
            return createRes ? Result.Ok : Result.Failed( "cannot create file" );
        }

        /// <summary>
        /// Reimports data to existing file using the previously chosen importer
        /// </summary>
        /// <param name="fullSourcePath"></param>
        /// <param name="fullDestPath"></param>
        /// <param name="importer"></param>
        public async Task<Result> ReImport( string fullSourcePath, string fullDestPath, Importer importer )
        {
            // NOTE: we use interop path syntax anyway, so it's ok to operate on InteropDataItem level                    
            Trace.Assert( fullDestPath.EndsWith( importer.Extension ), "Trying to reimport resource to a different type file" );
            InteropDataItem engineData = await _sharedData.EngineObjectRoot;

            string depotDestPath = _sharedData.DepotInfo.GetDepotPath( fullDestPath );
            InteropDataItem fileItem = (InteropDataItem)await engineData.DigUpSubItemAsync( "depot.files[\"" + depotDestPath + "\"]" );

            bool importRes = fileItem.IsValid 
                ? ( await fileItem.ExecuteAsync<bool>( "import", fullSourcePath, "" ) ).Value
                : false;

            return importRes ? Result.Ok : Result.Failed( "cannot reimport file" );
        }

        /// <summary>
        /// Creates a new resource and then imports data to it using the previously chosen importer
        /// </summary>
        /// <param name="fullSourcePath"></param>
        /// <param name="destDir"></param>
        /// <param name="importer"></param>
        public async Task<Result> Import( string fullSourcePath, string destDir, Importer importer )
        {
            InteropDataItem engineData = await _sharedData.EngineObjectRoot;
            string sourceFileName = System.IO.Path.GetFileNameWithoutExtension( fullSourcePath );
            string destPath = System.IO.Path.Combine( destDir, sourceFileName ) + "." + importer.Extension;
            string depotDestPath = _sharedData.DepotInfo.GetDepotPath( destPath );

            InteropDataItem fileItem = (InteropDataItem)await engineData.DigUpSubItemAsync( "depot.files[\"" + depotDestPath + "\"]" );

            var createRes = await fileItem.ExecuteAsync<bool>( "create", "" );
            if ( !createRes.Value )
            {
                return Result.Failed( "cannot create file" );
            }

            var importRes = await fileItem.ExecuteAsync<bool>( "import", fullSourcePath, "" );
            if ( !importRes.Value )
            {
                return Result.Failed( "cannot import file" );
            }

            return Result.Ok;
        }

        /// <summary>
        /// Saves a resource to a different file location
        /// Serializes a currently edited resource or just copies a file
        /// Destination path must not exist beforehand
        /// </summary>
        /// <param name="depotSrcPath"></param>
        /// <param name="fullDestPath"></param>
        public async Task<Result> SaveAs( string depotSrcPath, string fullDestPath )
        {
            InteropDataItem engineData = await _sharedData.EngineObjectRoot;
            string depotDestPath = _sharedData.DepotInfo.GetDepotPath( fullDestPath );

            InteropDataItem srcFileView = (InteropDataItem)await engineData.DigUpSubItemAsync( "depot.files[\"" + depotSrcPath + "\"]" );
            var saveRes = await srcFileView.ExecuteAsync<bool>( "saveAs", depotDestPath, "" );
            if ( !saveRes.Value )
            {
                return Result.Failed( "cannot save file" );
            }

            return Result.Ok;
        }
        
        #endregion Public methods

        #region Private methods

        private async Task<List<Importer>> GetImporters()
        {
            using ( await _importersLock.LockAsync() )
            {
                if ( _importers == null )
                {
                    InteropDataItem engineData = await _sharedData.EngineObjectRoot;
                    InteropDataItem importersView = (InteropDataItem)await engineData.DigUpSubItemAsync( "depot.importers" );
                    _importers = await InteropSerializer.Deserialize<List<Importer>>( importersView.Data );
                }
            }

            return _importers;
        }

        private async Task<List<Factory>> GetFactories()
        {
            using ( await _factoriesLock.LockAsync() )
            {
                if ( _factories == null )
                {
                    InteropDataItem engineData = await _sharedData.EngineObjectRoot;
                    InteropDataItem factoriesView = (InteropDataItem)await engineData.DigUpSubItemAsync( "depot.factories" );
                    _factories = await InteropSerializer.Deserialize<List<Factory>>( factoriesView.Data );
                }
            }

            return _factories;
        }

        #endregion Private methods

    }
}
